package com.hk.commons.http;

import com.hk.commons.http.utils.HttpUtils;
import com.hk.commons.util.ArrayUtils;
import com.hk.commons.util.CollectionUtils;
import com.hk.commons.util.JsonUtils;
import com.hk.commons.util.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.entity.mime.ContentBody;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.client5.http.ssl.TrustAllStrategy;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author kevin
 * @date 2019-8-9 15:58
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public abstract class HttpClientUtils {

    static final PoolingHttpClientConnectionManager CONNECTION_MANAGER;

    static final CloseableHttpClient DEFAULT_HTTP_CLIENT;

    static {
        SSLContext sslContext;
        try {
            sslContext = SSLContexts.custom()
                    .loadTrustMaterial(new TrustAllStrategy())
                    .build();
        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
            throw new UnsupportedOperationException(e);
        }
//        var socketFactoryRegistry = RegistryBuilder.<TlsSocketStrategy>create()
//                .register(URIScheme.HTTP.getId(), DefaultClientTlsStrategy.createDefault())
//                // 对于 https，忽略证书
//                .register(URIScheme.HTTPS.getId(), new DefaultClientTlsStrategy(sslContext))
//                .build();
        CONNECTION_MANAGER = PoolingHttpClientConnectionManagerBuilder.create()
                .setMaxConnTotal(1_000)
                .setMaxConnPerRoute(2_000)
                .setTlsSocketStrategy(new DefaultClientTlsStrategy(sslContext)) // 对于 https，忽略证书
                .setDefaultSocketConfig(SocketConfig.custom()
                        .setTcpNoDelay(true)
                        .setSoTimeout(Timeout.ofSeconds(5))
                        .build()
                )
                .setDefaultConnectionConfig(ConnectionConfig.custom()
                        .setConnectTimeout(5, TimeUnit.SECONDS) // 连接上服务器(握手成功)的时间，超出抛出connect timeout
                        .setValidateAfterInactivity(Timeout.ofSeconds(10))
                        .build())
                .build();
        DEFAULT_HTTP_CLIENT = createHttpClient(HttpUtils.DEFAULT_REQUEST_CONFIG);

    }

    public static CloseableHttpClient createHttpClient(RequestConfig requestConfig) {
        return createHttpClient(requestConfig, null);
    }

    @SneakyThrows
    public static CloseableHttpClient createHttpClient(RequestConfig requestConfig, String proxyHost) {
        return HttpClientBuilder.create()
                .setProxy(StringUtils.isEmpty(proxyHost) ? null : HttpHost.create(proxyHost))
                .setConnectionManager(CONNECTION_MANAGER)
                .evictExpiredConnections()
                .evictIdleConnections(TimeValue.of(3, TimeUnit.SECONDS))
                .evictExpiredConnections()
                .useSystemProperties()
                .setDefaultRequestConfig(requestConfig)
                .build();
    }

    /**
     * Http get 请求
     *
     * @param uri     请求地址
     * @param params  请求参数
     * @param headers 请求头
     * @return 字符串表现形式的响应内容
     */
    public static String get(String uri, Map<String, ?> params, Header... headers) throws IOException {
        var headerList = ArrayUtils.asArrayList(HttpUtils.DEFAULT_HEADER.getFirst());
        CollectionUtils.addAllNotNull(headerList, headers);
        return execute(DEFAULT_HTTP_CLIENT,
                HttpUtils.newHttpGet(uri, params, headerList.toArray(new Header[0])));
    }

    /**
     * Http delete 请求
     *
     * @param uri     请求地址
     * @param params  请求参数
     * @param headers 请求头
     * @return 字符串表现形式的响应内容
     */
    public static String delete(String uri, Map<String, ?> params, Header... headers) throws IOException {
        var headerList = ArrayUtils.asArrayList(HttpUtils.DEFAULT_HEADER.getFirst());
        CollectionUtils.addAllNotNull(headerList, headers);
        return execute(DEFAULT_HTTP_CLIENT,
                HttpUtils.newHttpDelete(uri, params, headerList.toArray(new Header[0])));
    }

    /**
     * http 请求执行
     *
     * @param httpClient httpClient
     * @param request    request
     * @return 字符串表现形式的响应内容
     */
    public static String execute(CloseableHttpClient httpClient, HttpUriRequest request) throws IOException {
        return execute(httpClient, request, UTF8HttpClientResponseHandler.getInstance());
    }

    /**
     * http 请求执行
     *
     * @param request         request
     * @param responseHandler 响应数据处理器
     * @return 响应内容
     */
    public static <T> T execute(HttpUriRequest request, HttpClientResponseHandler<T> responseHandler) throws
            IOException {
        return execute(DEFAULT_HTTP_CLIENT, request, responseHandler);
    }

    /**
     * http 请求执行
     *
     * @param httpClient      httpClient
     * @param request         request 方式
     * @param responseHandler 响应数据处理器
     * @return 响应内容
     */
    public static <T> T execute(CloseableHttpClient httpClient, HttpUriRequest request,
                                HttpClientResponseHandler<T> responseHandler) throws IOException {
        return httpClient.execute(request, responseHandler);

    }

    /**
     * Http Post 请求
     *
     * @param uri     请求地址
     * @param params  请求参数
     * @param headers 请求头
     */
    public static String simplePost(String uri, Map<String, ?> params, Header... headers) throws IOException {
        var headerList = ArrayUtils.asArrayList(HttpUtils.DEFAULT_HEADER.getFirst());
        CollectionUtils.addAllNotNull(headerList, headers);
        return execute(DEFAULT_HTTP_CLIENT, HttpUtils.newHttpPost(uri,
                HttpUtils.toHttpEntity(params), headerList.toArray(new Header[0])));
    }

    /**
     * Http Post 请求，请求头包含 Content-Type =  application/json;charset=utf-8
     *
     * @param uri     请求地址
     * @param body    请求体
     * @param headers 请求头
     */
    public static String jsonPost(String uri, Object body, Header... headers) throws IOException {
        var headerList = ArrayUtils.asArrayList(HttpUtils.DEFAULT_HEADER.getFirst(),
                new BasicHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()));
        CollectionUtils.addAllNotNull(headerList, headers);
        String bodyString = JsonUtils.serialize(body);
        return execute(DEFAULT_HTTP_CLIENT,
                HttpUtils.newHttpPost(uri, StringUtils.hasLength(bodyString) ?
                                new StringEntity(bodyString, StandardCharsets.UTF_8) : null,
                        headerList.toArray(new Header[0])));
    }

    /**
     * Http Post 文件上传的请求
     *
     * @param uri         请求地址
     * @param contentBody 请求体
     * @param headers     请求头
     */
    public static String mimePost(String uri, Map<String, ContentBody> contentBody, Header... headers) throws
            IOException {
        var headerList = ArrayUtils.asArrayList(HttpUtils.DEFAULT_HEADER.getFirst());
        CollectionUtils.addAllNotNull(headerList, headers);
        return execute(DEFAULT_HTTP_CLIENT,
                HttpUtils.newHttpPost(uri, HttpUtils.multipartHttpEntity(contentBody),
                        headerList.toArray(new Header[0])));
    }

}
